package com.only4play.common.utils;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import java.time.*;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.TemporalField;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 日期工具
 *
 * @author liyuncong
 * @date 2023/11/06 14:31
 **/
public class DateUtils {

    private static final ZoneId ZONEID = ZoneId.systemDefault();

    private static final long WEEK_MAX_CACHE_SIZE = 100L;
    private static final long CACHE_TIMEOUT_HOURS = 2L;
    private static final int MAX_WEEKS = 55;
    private static final int SEVEN = 7;

    public record WeekInfo(int year, int weekNo, LocalDate monday, LocalDate sunday) {
    }

    public record LocaleYear(Locale locale, Integer year) {
    }

    static final LoadingCache<LocaleYear, List<WeekInfo>> WEEK_CACHE = CacheBuilder.newBuilder()
            .expireAfterAccess(CACHE_TIMEOUT_HOURS, TimeUnit.HOURS)
            .maximumSize(WEEK_MAX_CACHE_SIZE)
            .build(new CacheLoader<>() {
                @Override
                public List<WeekInfo> load(LocaleYear localeYear) throws Exception {
                    LocalDate firstDay = LocalDate.of(localeYear.year, 1, 1);
                    LocalDate monday =
                            firstDay.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
                    LocalDate sunday =
                            firstDay.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
                    List<WeekInfo> result = new ArrayList<>(MAX_WEEKS);
                    int weekNo = 1;
                    result.add(new WeekInfo(localeYear.year, weekNo, monday, sunday));
                    do {
                        weekNo += 1;
                        monday = monday.plusDays(SEVEN);
                        sunday = monday.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
                        result.add(new WeekInfo(localeYear.year, weekNo, monday, sunday));
                    } while (sunday.getYear() <= localeYear.year);
                    return result;
                }
            });

    /**
     * LocalDateTime to Date.
     */
    public static Date asDate(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(ZONEID).toInstant());
    }

    /**
     * LocalDate to Date.
     */
    public static Date asDate(LocalDate localDate) {
        return Date.from(
                localDate.atStartOfDay(ZONEID).toInstant()
        );
    }

    /**
     * String to Date.
     */
    public static Date asDate(String date) {
        return null;
    }

    /**
     * Date to LocalDateTime.
     */
    public static LocalDateTime asLocalDateTime(Date date) {
        return Instant.ofEpochMilli(date.getTime()).atZone(ZONEID).toLocalDateTime();
    }

    /**
     * LocalDate to LocalDateTime.
     */
    public static LocalDateTime asLocalDateTime(LocalDate localDate) {
        return localDate.atStartOfDay();
    }

    /**
     * String to LocalDateTime.
     */
    public static LocalDateTime asLocalDateTime(String date) {
        return null;
    }

    public static int weekNo() {
        Locale locale = Locale.getDefault();
        LocalDate date = LocalDate.now();
        TemporalField woy = WeekFields.of(locale).weekOfWeekBasedYear();
        return date.get(woy);
    }

    public static int weekNo(final LocalDate date, final Locale locale) {
        TemporalField woy = WeekFields.of(locale).weekOfWeekBasedYear();
        return date.get(woy);
    }

    public static WeekInfo getWeekInfo() throws ExecutionException {
        Locale locale = Locale.getDefault();
        int year = LocalDate.now().getYear();
        int weekNo = weekNo(LocalDate.now(), locale);
        return getWeekInfo(locale, year, weekNo);
    }

    public static WeekInfo getWeekInfo(final Locale locale, final int year, final int weekNo)
            throws ExecutionException {
        LocaleYear localeYear = new LocaleYear(locale, year);
        var weekInfoList = WEEK_CACHE.get(localeYear);
        return weekInfoList.stream()
                .filter(item -> item.year == year && item.weekNo == weekNo)
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Invalid WeekNo"));
    }

}
